﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Arcana.Contract.Argument
{
    /// <summary>
    /// Represents the base class for all contracts that validate arguments.
    /// </summary>
    /// <remarks>
    /// <b>Note to implementers: </b> Proper roles should be applied by each implementing class. See
    /// <see cref="Roles"/> for more information on this topic. Also, the argument value MUST be obtained through
    /// <see cref="ContractBase.GetArgument"/> method.
    /// </remarks>
    [Serializable]
    public abstract class ContractBase : PostSharp.Aspects.OnMethodBoundaryAspect, IContract
    {
        private string _parameterName;
        private int _parameterIndex;
        private Type _exception;

        /// <summary>
        /// Initializes a new instance of <b>ConstructorBase</b>.
        /// </summary>
        /// <remarks>
        /// This constructor initializes a new instance of <b>ConstructorBase</b> that it's purpose is to provide functionality
        /// for all argument contracts. It takes a <i>parameterName</i> and caches it's index for further use. Also implements
        /// <see cref="IContract"/> interface.
        /// </remarks>
        /// <param name="parameterName">The parameter name to which argument contract applies.</param>
        /// <exception cref="ArgumentNullException"><i>parameterName</i> is <b>null</b>.</exception>
        public ContractBase(string parameterName)
        {
            if (parameterName == null) throw new ArgumentNullException();
            _parameterName = parameterName;
            _parameterIndex = -1;
            _exception = typeof(System.ArgumentException);
        }

        /// <summary>
        /// Gets the associated argument from execution arguments.
        /// </summary>
        /// <param name="args">The method execution arguments of current context.</param>
        /// <returns>The object that is passed as an argument.</returns>
        /// <exception cref="InvalidOperationException"><i>parameterName</i> was not found.</exception>
        protected virtual object GetArgument(PostSharp.Aspects.MethodExecutionArgs args)
        {
            if (_parameterIndex < 0)
            {
                var parameters = args.Method.GetParameters();
                for (int i = parameters.Length - 1; i >= 0; i--)
                {
                    if (parameters[i].Name == _parameterName)
                    {
                        _parameterIndex = i;
                        break;
                    }
                }
                if (_parameterIndex < 0)
                {
                    throw new InvalidOperationException("Argument " + _parameterName + " was not found.");
                }
            }
            return args.Arguments.GetArgument(_parameterIndex);
        }

        /// <summary>
        /// Occurs when method enters execution, and validates the argument against argument contract.
        /// </summary>
        /// <remarks>
        /// <b>Note To Implementers:</b> You have to override <see cref="ValidateArgument"/> method. This method
        /// will throw exception using <see cref="ThrowException"/> if validation fails.
        /// </remarks>
        public sealed override void OnEntry(PostSharp.Aspects.MethodExecutionArgs args)
        {
            if (!ValidateArgument(GetArgument(args)))
            {
                ThrowException();
            }
        }

        /// <summary>
        /// Performs validation on the argument.
        /// </summary>
        /// <param name="argument">The argument that should be validated.</param>
        /// <returns><b>true</b> if <i>argument</i> is validated, otherwise <b>false</b>.</returns>
        protected abstract bool ValidateArgument(object argument);

        /// <summary>
        /// Throws exception appointed by <see cref="ContractBase.Exception"/>
        /// </summary>
        protected virtual void ThrowException()
        {
            throw Exception.GetConstructor(new Type[] { }).Invoke(new object[] { }) as System.Exception;
        }

        /// <summary>
        /// Represents type of exception to throw if contract validation fails.
        /// </summary>
        /// <value>
        /// The type of exception to throw, if the contract validation fails.
        /// </value>
        /// <remarks>
        /// This value must inherit from <see cref="System.Exception"/> class. This value is set to
        /// <see cref="System.ArgumentException"/> by default.
        /// </remarks>
        /// <exception cref="ArgumentNullException"><i>value</i> is <b>null</b>.</exception>
        /// <exception cref="ArgumentException"><i>value</i> does not inherit from <see cref="System.Exception"/></exception>
        public Type Exception
        {
            get
            {
                return _exception;
            }
            set
            {
                if (value == null) throw new ArgumentNullException();
                if (!value.IsSubclassOf(typeof(System.Exception))) throw new ArgumentException();
                _exception = value;
            }
        }
    }
}
